/*
 * CGameBase.cpp
 *
 *  Created on: Sep 1, 2011
 *      Author: welcome
 */

#include "CGameBase.h"

namespace GGEngine {
KD_API KDust KD_APIENTRY GetCurrentTimeInMilliseconds() {
	return kdGetTimeUST() / 1000000LL;
}
CGameBase* CGameBase::appInstance = KD_NULL;

int CGameBase::bgmusic = 0;

KDtime CGameBase::frameTime = 0;

CGameBase::CGameBase() :
	isQuitting(false), isPaused(false), isFocused(true) {

	m_cameras = KD_NULL;

	m_light = KD_NULL;

	m_fog = KD_NULL;

	appInstance = KD_NULL;

	window = KD_NULL;

	eglDisplay = EGL_NO_DISPLAY;

	eglWindowSurface = EGL_NO_SURFACE;

	eglContextOpenGLES = EGL_NO_CONTEXT;

	screenWidth = 0;

	screenHeight = 0;

	setCameraCount(1);

	setActiveCamera(0);

	pUIManager = acUIManager::GetInstance();

	lastTime = GetCurrentTimeInMilliseconds();

	Initialize();
}

CGameBase* CGameBase::GetInstance() {
	if (!appInstance)
		appInstance = new CGameBase();
	return appInstance;
}

CGameBase::~CGameBase() {
	Release();
}
//! Can be used to enable or disable multitouch events
void CGameBase::SetMultitouchEnable(bool enabled) {
	KDint enable[] = { 1 };
	if (!enabled)
		enable[0] = 0;
	kdOutputSeti(KD_OUTPUT_TOUCH_ENABLE_EXT, 1, enable);
}

KDtime CGameBase::GetFrametime() {
	return frameTime;
}
int CGameBase::GetDeltaTime() {
	return pUIManager->GetDeltaTime();
}
void CGameBase::AddPage(acPage* page) {
	pUIManager->GetContent()->AddPage(page);
	pUIManager->SetCurrentPage(page);
}

KDint CGameBase::Initialize() {
	/* Initialize OpenGL ES render context on the KDWindow using EGL  */
	if (InitializeGraphics() != 0) {
		return -1;
	}
	GetWindowSize(&screenWidth, &screenHeight);

	if (pUIManager->Initialize(eglConfig, screenWidth, screenHeight) == false) {
		pUIManager->Release();
		return -1;
	}
	if (CAudioManager::init() != 0) {
		;
		return -1;
	}
	return 0;
}

KDint CGameBase::Update(int deltaTime) {
	printf("Enter update site");
	KDint updates = 0;
	KDtime currentTime = GetCurrentTimeInMilliseconds();
	while (lastTime < currentTime) {
		// allow up to 2 updates per frame
		if (updates < 1) {
			if (pUIManager == KD_NULL) {
				return -1;
			} else {
				OnUpdate(deltaTime);
				pUIManager->Update(deltaTime);
			}
			frameTime += 1000 / GameSpec::FPS;
		}
		lastTime += 1000 / GameSpec::FPS;
		updates++;
	}
	return 0;
}

void CGameBase::Release() {
	if (m_light) {
		delete m_light;
		m_light = NULL;
	}

	if (m_cameras) {
		delete[] m_cameras;
	}

	if (m_fog)
		delete m_fog;

	if (pUIManager) {
		pUIManager->Release();
		pUIManager = NULL;
	}
	ReleaseGraphics();
	CAudioManager::destroy();
}

KDint CGameBase::SwapBuffers() {
	if (eglSwapBuffers(eglDisplay, eglWindowSurface) != EGL_TRUE) {
		EGLint ec = eglGetError();

		if (ec == EGL_CONTEXT_LOST) {
			if (RecreateEGLContext() != EGL_TRUE) {
				printf("gfxSwapBuffers: Failed to recreate EGLContext.");
				return -1;
			}
		} else {
			printf("gfxSwapBuffers: eglSwapBuffers failed");
			return -1;
		}
	}
	return 0;
}

KDint CGameBase::ResumeGraphics(void) {
	if (eglMakeCurrent(eglDisplay, eglWindowSurface, eglWindowSurface,
			eglContextOpenGLES) != EGL_TRUE) {
		EGLint ec = eglGetError();

		if (ec == EGL_CONTEXT_LOST) {
			if (RecreateEGLContext() != EGL_TRUE) {
				kdLogMessage("gfxResumeGraphics: Failed to recreate EGLContext.");
				return -1;
			}
		} else {
			kdLogMessage("gfxResumeGraphics: Unable to resume.");
			return -1;
		}
	}
	return 0;
}

acUIManager *CGameBase::GetUIManager() {
	return pUIManager;
}

/*
 * Miscellany methods
 */
KDint CGameBase::InitializeGraphics(void) {
	if (InitializeEGLConfig() != 0) {
		return -1;
	}
	if (InitializeWindow() != 0) {
		ReleaseGraphics();
		return -1;
	}
	if (InitializeGLES() != 0) {
		ReleaseGraphics();
		return -1;
	}
	return 0;
}

void CGameBase::ReleaseGraphics(void) {
	eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE,
			EGL_NO_CONTEXT);
	if (eglContextOpenGLES != EGL_NO_CONTEXT) {
		eglDestroyContext(eglDisplay, eglContextOpenGLES);
		eglContextOpenGLES = EGL_NO_CONTEXT;
	}
	if (eglWindowSurface != EGL_NO_SURFACE) {
		eglDestroySurface(eglDisplay, eglWindowSurface);
		eglWindowSurface = EGL_NO_SURFACE;
	}
	if (eglDisplay != EGL_NO_DISPLAY) {
		eglReleaseThread();
		eglTerminate(eglDisplay);
		eglDisplay = EGL_NO_DISPLAY;
	}
	if (window != KD_NULL) {
		kdDestroyWindow(window);
		window = KD_NULL;
	}
}

/* Create a KDWindow */
KDint CGameBase::InitializeWindow() {
	window = kdCreateWindow(eglDisplay, eglConfig, KD_NULL);
	if (!window) {
		;
		return -1;
	}
	return 0;
}

KDint CGameBase::InitializeEGLConfig() {
	EGLint majorVersion = 0;
	EGLint minorVersion = 0;
	EGLint numConfigs = 0;
	/* Set the attributes for EGLConfig. */
	static const EGLint s_configAttribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT
			| EGL_PBUFFER_BIT, EGL_STENCIL_SIZE, 8, EGL_DEPTH_SIZE, 16,
			EGL_NONE };
	eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
	if (eglDisplay == EGL_NO_DISPLAY) {
		kdLogMessage("InitializeEGLConfig: eglGetDisplay failed");
		return -1;
	}
	if (eglInitialize(eglDisplay, &majorVersion, &minorVersion) == EGL_FALSE) {
		kdLogMessage("InitializeEGLConfig: eglInitialize failed");
		return -1;
	}
	eglChooseConfig(eglDisplay, s_configAttribs, &eglConfig, 1, &numConfigs);
	if (numConfigs == 0) {
		;
		eglTerminate(eglDisplay);
		return -1;
	}
	return 0;
}

/* Initializes the OpenGL ES context into a KDWindow */
KDint CGameBase::InitializeGLES() {
	/* Set the attributes for the window surface */
	static const EGLint s_surfaceAttribs[] = { EGL_NONE };
	EGLNativeWindowType nativeType;
	if (kdRealizeWindow(window, &nativeType) != 0) {
		;
		return -1;
	}
	/* Create a context for OpenGL ES */
	eglContextOpenGLES = eglCreateContext(eglDisplay, eglConfig,
			EGL_NO_CONTEXT, KD_NULL);
	if (eglContextOpenGLES == EGL_NO_CONTEXT) {
		EGLint error = eglGetError();
		if (error != EGL_SUCCESS) {
			kdLogMessage("InitializeGLES: Failed to set EGL context");
			return -1;
		}
	}
	/* Create a window surface for OpenGL ES */
	eglWindowSurface = eglCreateWindowSurface(eglDisplay, eglConfig,
			nativeType, s_surfaceAttribs);
	if (eglWindowSurface == EGL_NO_SURFACE) {
		kdLogMessage("InitializeGLES: Unable to create window surface for OpenGL ES");
		return -1;
	}
	if (eglMakeCurrent(eglDisplay, eglWindowSurface, eglWindowSurface,
			eglContextOpenGLES) != EGL_TRUE) {
		kdLogMessage("InitializeGLES: Call to eglMakeCurrent failed! Continuing anyway..");
	}
	return 0;
}

void CGameBase::GetWindowSize(int *outWidth, int *outHeight) {
	eglQuerySurface(eglDisplay, eglWindowSurface, EGL_WIDTH,
			(EGLint *) outWidth);
	eglQuerySurface(eglDisplay, eglWindowSurface, EGL_HEIGHT,
			(EGLint *) outHeight);
}

EGLBoolean CGameBase::RecreateEGLContext(void) {
	eglDestroyContext(eglDisplay, eglContextOpenGLES);
	eglContextOpenGLES = eglCreateContext(eglDisplay, eglConfig,
			EGL_NO_CONTEXT, KD_NULL);
	if (eglContextOpenGLES == EGL_NO_CONTEXT) {
		kdLogMessage("RecreateEGLContext: Failed to create EGLContext");
		return EGL_FALSE;
	}
	if (eglMakeCurrent(eglDisplay, eglWindowSurface, eglWindowSurface,
			eglContextOpenGLES) != EGL_TRUE) {
		kdLogMessage("RecreateEGLContext: eglMakeCurrent failed");
		return EGL_FALSE;
	}
	return EGL_TRUE;
}

int CGameBase::PlayBgMusic(const char *fileName) {
	bgmusic = CAudioManager::playAudio(fileName, -1);
	return bgmusic;
}

int CGameBase::PauseBgMusic() {
	CAudioManager::pause(bgmusic);
	return 0;
}

int CGameBase::ResumeBgMusic() {
	CAudioManager::resume(bgmusic);
	return 0;
}

int CGameBase::SkiptimeBgMusic() {
	CAudioManager::skiptime();
	return 0;
}

void CGameBase::setLight(CLight *light) {
	m_light = light;
	if (m_light != NULL) {
		glEnable(GL_LIGHTING);
		glEnable(GL_NORMALIZE);
		glEnable(GL_COLOR_MATERIAL);
		m_light->initLight();
	} else {
		glDisable(GL_LIGHTING);
		glDisable(GL_NORMALIZE);
		glDisable(GL_COLOR_MATERIAL);
	}
}
/*
 * Camera functionalities
 */
void CGameBase::setCameraCount(int cameraCount) {
	if (m_cameras != NULL) {
		delete[] m_cameras;
		m_cameras = NULL;
	}
	if (cameraCount > 0) {
		m_cameraCount = cameraCount;
		m_cameras = new CCamera[m_cameraCount];
	}
}

int CGameBase::getCameraCount() {
	return m_cameraCount;
}

CCamera *CGameBase::getCamera(int cameraIndex) {
	if (cameraIndex >= m_cameraCount)
		return KD_NULL;
	return &m_cameras[cameraIndex];
}

void CGameBase::setActiveCamera(int cameraIndex) {
	if (cameraIndex >= m_cameraCount)
		return;
	m_cameraIndex = cameraIndex;
	m_cameras[m_cameraIndex].lookAt();
}

CCamera *CGameBase::getActiveCamera() {
	return &m_cameras[m_cameraIndex];
}

void CGameBase::setFog(CFog *fog) {
	this->m_fog = fog;
	if (m_fog != KD_NULL) {
		glEnable(GL_FOG);
		m_fog->initFog();
	} else
		glDisable(GL_FOG);
}

// returns the index of the touch if found, -1 if not
KDint CGameBase::FindTouch(const KDEvent *event) {
	const KDEventInputTouchEXT &eventTouch = event->data.inputtouchEXT;
	KDint index = eventTouch.index;

	KDsize size = touches.size();
	for (KDuint i = 0; i < size; i++) {
		Touch &t = touches[i];
		if (t.index == index)
			return i;
	}

	return -1;
}

void CGameBase::AddTouch(const KDEvent *event) {
	const KDEventInputTouchEXT &eventTouch = event->data.inputtouchEXT;

	KDint index = eventTouch.index;

	Touch t;
	t.index = index;
	t.x = eventTouch.coords->x;
	t.y = eventTouch.coords->y;
	t.timestamp = event->timestamp;
	touches.push_back(t);

	bool first = false;
	if (touches.size() == 1)
		first = true;

	OnTouchBegin(first, &touches[touches.size() - 1]);
}

void CGameBase::RemoveTouch(const KDEvent *event) {
	const KDEventInputTouchEXT &eventTouch = event->data.inputtouchEXT;

	KDint32 i = FindTouch(event);
	if (i != -1) {
		bool last = false;
		if (eventTouch.touchstate & KD_TOUCH_LAST_EXT)
			last = true;
		OnTouchEnd(last, &touches[i]);
		touches.erase(touches.begin() + i);
	}
}

void CGameBase::ModifyTouch(const KDEvent *event) {
	const KDEventInputTouchEXT &eventTouch = event->data.inputtouchEXT;

	KDint i = FindTouch(event);
	if (i != -1) {
		touches[i].x = eventTouch.coords->x;
		touches[i].y = eventTouch.coords->y;
		OnTouchMove(&touches[i]);
	}
}

void CGameBase::handle(const KDEvent *event) {

	switch (event->type) {

	case KD_EVENT_QUIT:
		isQuitting = true;
		break;
	case KD_EVENT_PAUSE:
		isPaused = true;
		OnPause();
		break;
	case KD_EVENT_RESUME:
		isPaused = false;
		OnResume();
		break;
	case KD_EVENT_WINDOW_FOCUS:
		if (event->data.windowfocus.focusstate == 0) {
			isFocused = false;
		} else {
			isFocused = true;
		}
		OnWindowFocus(event->data.windowfocus.focusstate);
		break;
	case KD_EVENT_WINDOW_CLOSE:
		isQuitting = true;
		break;
	case KD_EVENT_INPUT_POINTER: {
		KDint pointerX = event->data.inputpointer.x;
		KDint pointerY = event->data.inputpointer.y;
		KDint index = event->data.inputpointer.index;
		KDint select = event->data.inputpointer.select;
		switch (index) {
		case KD_INPUT_POINTER_X:
		case KD_INPUT_POINTER_Y:
			if (select) {
				pUIManager->TouchMove(pointerX, pointerY);
			}
			OnPointerMove(pointerX, pointerY, select);
			break;
		case KD_INPUT_POINTER_SELECT:
			if (select == 0) // up
			{
				pUIManager->TouchUp(pointerX, pointerY);
			} else if (select == 1) // down
			{
				pUIManager->TouchDown(pointerX, pointerY);
			}
			OnPointerSelect(pointerX, pointerY, select);
			break;
		default:
			break;
		}
		break;
	}
	case KD_EVENT_INPUT_TOUCH_EXT: {
		const KDEventInputTouchEXT &eventTouch = event->data.inputtouchEXT;
		KDint32 index = eventTouch.index;
		KDfloat32 x = eventTouch.coords->x;
		KDfloat32 y = eventTouch.coords->y;
		acUIManager * pUIManager = acUIManager::GetInstance();
		if (event->data.inputtouchEXT.touchstate & KD_TOUCH_FIRST_EXT) {
			pUIManager->TouchDown(x, y, 0, index);
			AddTouch(event);
		} else if (event->data.inputtouchEXT.touchstate & KD_TOUCH_LAST_EXT) {
			pUIManager->TouchUp(x, y, index);
			RemoveTouch(event);
		} else
			pUIManager->TouchMove(x, y, index);
		ModifyTouch(event);
		break;
	}
	case KD_EVENT_INPUT: {
		switch (event->data.input.index) {
		case KD_INPUT_GAMEKEYS_UP:
		case KD_INPUT_GAMEKEYS_LEFT:
		case KD_INPUT_GAMEKEYS_RIGHT:
		case KD_INPUT_GAMEKEYS_DOWN:
		case KD_INPUT_GAMEKEYS_A:
		case KD_INPUT_GAMEKEYS_FIRE:
			if (event->data.input.value.i == 0) {
				pUIManager->KeyDown(event->data.input.index);
			} else {
				pUIManager->KeyUp(event->data.input.index);
			}
			break;
		}
		OnKey(event->data.input.index, event->data.input.value.i);
		break;
	}
	}
	OnEvent(event);
}
void CGameBase::EventCallback(const KDEvent *e) {
	CGameBase *me = CGameBase::GetInstance();
	if (e) {
		if (me)
			me->handle(e);
		kdDefaultEvent(e);
	}
}
/*
 * Main loop
 */
void CGameBase::MainLoop() {
	for (;;) {
		for (;;) {
			const KDEvent *e = kdWaitEvent((isPaused || !isFocused) ? -1 : 0);
			if (e == KD_NULL)
				break;
			if (e) {
				EventCallback(e);
				if (isQuitting)
					break;
			}
		}
		if (isQuitting)
			break;

		if (!isPaused && isFocused) {

			int deltaTime = GetDeltaTime();

			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

			glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

			if (Update(deltaTime) != 0)
				break;

			SwapBuffers();
		}
	}
}
} /* namespace GGEngine */
